package org.wggds.webservices.io;

import java.io.StringReader;
import java.util.List;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.wggds.webservices.io.data.CompleteInformation;
import org.wggds.webservices.io.data.CompositionComponent;
import org.wggds.webservices.io.data.ListOperation;
import org.wggds.webservices.io.data.OutputFormat;
import org.wggds.webservices.io.data.Persubstitution;
import org.wggds.webservices.io.query.CompositionSearchQuery;
import org.wggds.webservices.io.query.IdListQuery;
import org.wggds.webservices.io.query.ListOperationQuery;
import org.wggds.webservices.io.query.MassSearchQuery;
import org.wggds.webservices.io.query.SubstructureQuery;

public class WggdsQueryInputUtil 
{
    /**
     * <?xml version="1.0" encoding="UTF-8"?>
     * <query completeInformation="false" reducingEnd="true" terminal="false" exactMatch="true" otherResidues="true"
     *          ignoreReducingAlditol="false" format="xml">
     *   <sequence>
     *   ...
     *   </sequence>
     * </query>
     */
    @SuppressWarnings("unchecked")
    public static SubstructureQuery parseSubstructureQuery(String a_query) throws WggdsQueryExcpetion
    {
        SubstructureQuery t_result = new SubstructureQuery();
        SAXBuilder builder = new SAXBuilder();
        Document t_objDocument = null;
        try
        {
            t_objDocument = builder.build(new StringReader(a_query));
        }
        catch (Exception t_exection)
        {
            throw new WggdsQueryExcpetion(t_exection.getMessage(),t_exection);
        }
        Element t_root = t_objDocument.getRootElement();
        if ( !t_root.getName().equals("query") )
        {
            throw new WggdsQueryExcpetion("Missing query tag in xml.");
        }
        // boolean attributes
        t_result.setCompleteInformation(WggdsQueryInputUtil.parseCompleteInformation("completeInformation", t_root.getAttributeValue("completeInformation")));
        t_result.setReducingEnd(WggdsQueryInputUtil.parseRequiredBoolean("reducingEnd", t_root.getAttributeValue("reducingEnd")));
        t_result.setTerminal(WggdsQueryInputUtil.parseRequiredBoolean("terminal", t_root.getAttributeValue("terminal")));
        t_result.setExactMatch(WggdsQueryInputUtil.parseRequiredBoolean("exactMatch", t_root.getAttributeValue("exactMatch")));
        t_result.setOtherResidues(WggdsQueryInputUtil.parseRequiredBoolean("otherResidues", t_root.getAttributeValue("otherResidues")));
        t_result.setIgnoreReducingAlditol(WggdsQueryInputUtil.parseRequiredBoolean("ignoreReducingAlditol", t_root.getAttributeValue("ignoreReducingAlditol")));
        // format
        t_result.setFormat(WggdsQueryInputUtil.parseFormat(t_root.getAttributeValue("format")));
        // substructure
        List<Element> t_childs = t_root.getChildren("sequence");
        if ( t_childs.size() != 1 )
        {
            throw new WggdsQueryExcpetion("Missing or to many tag sequence.");
        }
        for (Element t_element : t_childs)
        {
            t_result.setSequence(t_element.getText());
        }
        return t_result;
    }

    private static OutputFormat parseFormat(String a_value) throws WggdsQueryExcpetion
    {
        if ( a_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute format.");
        }
        if ( a_value.trim().equals("xml") )
        {
            return OutputFormat.XML;
        }
        if ( a_value.trim().equals("json") )
        {
            return OutputFormat.JSON;
        }
        throw new WggdsQueryExcpetion("Invalid value for attribute format (required: xml or json).");
    }

    private static boolean parseRequiredBoolean(String a_attributeName, String a_value) throws WggdsQueryExcpetion
    {
        if ( a_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute " + a_attributeName + ".");
        }
        if ( a_value.trim().equals("true") )
        {
            return true;
        }
        if ( a_value.trim().equals("false") )
        {
            return false;
        }
        throw new WggdsQueryExcpetion("Invalid value for attribute " + a_attributeName + " (required: true or false).");
    }

    /**
     * <?xml version="1.0" encoding="UTF-8"?>
     * <query completeInformation="false" mass="960.2" interval="300.0" ppm="true" persubstitution="none" monoisotopic="true" format="xml"/>
     */
    public static MassSearchQuery parseMassSearchQuery(String a_query) throws WggdsQueryExcpetion
    {
        MassSearchQuery t_result = new MassSearchQuery();
        SAXBuilder builder = new SAXBuilder();
        Document t_objDocument = null;
        try
        {
            t_objDocument = builder.build(new StringReader(a_query));
        }
        catch (Exception t_exection)
        {
            throw new WggdsQueryExcpetion(t_exection.getMessage(),t_exection);
        }
        Element t_root = t_objDocument.getRootElement();
        if ( !t_root.getName().equals("query") )
        {
            throw new WggdsQueryExcpetion("Missing query tag in xml.");
        }
        // boolean attributes
        t_result.setCompleteInformation(WggdsQueryInputUtil.parseCompleteInformation("completeInformation", t_root.getAttributeValue("completeInformation")));
        t_result.setIntervalPPM(WggdsQueryInputUtil.parseRequiredBoolean("ppm", t_root.getAttributeValue("ppm")));
        t_result.setMonoisotopic(WggdsQueryInputUtil.parseRequiredBoolean("monoisotopic", t_root.getAttributeValue("monoisotopic")));
        // double values
        t_result.setMass(WggdsQueryInputUtil.parseRequiredDouble("mass",t_root.getAttributeValue("mass")));
        t_result.setInterval(WggdsQueryInputUtil.parseRequiredDouble("interval",t_root.getAttributeValue("interval")));
        // persubstitution
        String t_value = t_root.getAttributeValue("persubstitution");
        if ( t_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute persubstitution.");
        }
        t_result.setPersubstitution(Persubstitution.forName(t_value));
        // format
        t_result.setFormat(WggdsQueryInputUtil.parseFormat(t_root.getAttributeValue("format")));
        return t_result;
    }

    private static Double parseRequiredDouble(String a_attributeName, String a_value) throws WggdsQueryExcpetion
    {
        if ( a_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute " + a_attributeName + ".");
        }
        try
        {
            return Double.parseDouble(a_value.trim());
        } 
        catch (Exception t_tExection)
        {
            throw new WggdsQueryExcpetion("Invalid value for attribute " + a_attributeName + " (not a number).");
        }
    }

    /**
     * <?xml version="1.0"?>
     * <query completeInformation="true" allowOtherResidues="true" format="xml">
     *    <component minValue="0" maxValue="2">
     *        <![CDATA[...]]>
     *    </component>
     *    <component minValue="0" maxValue="2">
     *        <![CDATA[...]]>
     *    </component>
     * </query> 
     */
    @SuppressWarnings("unchecked")
    public static CompositionSearchQuery parseCompositionSearchQuery(String a_query) throws WggdsQueryExcpetion
    {
        CompositionSearchQuery t_result = new CompositionSearchQuery();
        SAXBuilder builder = new SAXBuilder();
        Document t_objDocument = null;
        try
        {
            t_objDocument = builder.build(new StringReader(a_query));
        }
        catch (Exception t_exection)
        {
            throw new WggdsQueryExcpetion(t_exection.getMessage(),t_exection);
        }
        Element t_root = t_objDocument.getRootElement();
        if ( !t_root.getName().equals("query") )
        {
            throw new WggdsQueryExcpetion("Missing query tag in xml.");
        }
        // boolean attributes
        t_result.setCompleteInformation(WggdsQueryInputUtil.parseCompleteInformation("completeInformation", t_root.getAttributeValue("completeInformation")));
        t_result.setAllowOtherResidues(WggdsQueryInputUtil.parseRequiredBoolean("allowOtherResidues", t_root.getAttributeValue("allowOtherResidues")));
        // format
        t_result.setFormat(WggdsQueryInputUtil.parseFormat(t_root.getAttributeValue("format")));
        // components
        List<Element> t_childs = t_root.getChildren("component");
        if ( t_childs.size() < 1 )
        {
            throw new WggdsQueryExcpetion("Missing component tag.");
        }
        for (Element t_element : t_childs)
        {
            t_result.addComponent(WggdsQueryInputUtil.parseComponent(t_element));
        }
        return t_result;
    }

    /**
     * <component minValue="0" maxValue="2">
     *        <![CDATA[...]]>
     * </component>
     */
    private static CompositionComponent parseComponent(Element a_element) throws WggdsQueryExcpetion
    {
        CompositionComponent t_component = new CompositionComponent();
        String t_value = a_element.getAttributeValue("minValue");
        if (t_value == null)
        {
            t_component.setMinValue(CompositionComponent.INDEFINITE);
        }
        else
        {
            t_component.setMinValue(WggdsQueryInputUtil.parsePosInt("minValue", t_value));
        }
        t_value = a_element.getAttributeValue("maxValue");
        if (t_value == null)
        {
            t_component.setMaxValue(CompositionComponent.INDEFINITE);
        }
        else
        {
            t_component.setMaxValue(WggdsQueryInputUtil.parsePosInt("maxValue", t_value));
        }
        t_value = a_element.getText();
        if (t_value == null)
        {
            throw new WggdsQueryExcpetion("Missing glyde description in component tag.");
        }
        else
        {
            t_component.setResidue(t_value);
        }
        if ( t_component.getMinValue() > t_component.getMaxValue() )
        {
            throw new WggdsQueryExcpetion("The minValue can not be larger than maxValue in component tag.");
        }
        return t_component;
    }

    private static Integer parsePosInt(String a_attributeName, String a_value) throws WggdsQueryExcpetion
    {
        Integer t_result = null;
        try
        {
            t_result = Integer.parseInt(a_value);
        } 
        catch (Exception t_exection)
        {
            throw new WggdsQueryExcpetion(a_attributeName + " is not a positive number.");
        }
        if ( t_result < 0 )
        {
            throw new WggdsQueryExcpetion(a_attributeName + " is not a positive number.");
        }
        return t_result;
    }

    /**
     * <?xml version="1.0" encoding="UTF-8"?>
     * <query completeInformation="false" listOne="2010.07.29_A23DFC56V" listTwo="2010.07.27_C56VQ23ER" operation="AND" format="xml"/>
     * @throws WggdsQueryExcpetion 
     */
    public static ListOperationQuery parseListOperationQuery(String a_query) throws WggdsQueryExcpetion
    {
        ListOperationQuery t_result = new ListOperationQuery();
        SAXBuilder builder = new SAXBuilder();
        Document t_objDocument = null;
        try
        {
            t_objDocument = builder.build(new StringReader(a_query));
        }
        catch (Exception t_exection)
        {
            throw new WggdsQueryExcpetion(t_exection.getMessage(),t_exection);
        }
        Element t_root = t_objDocument.getRootElement();
        if ( !t_root.getName().equals("query") )
        {
            throw new WggdsQueryExcpetion("Missing query tag in xml.");
        }
        // boolean attributes
        t_result.setCompleteInformation(WggdsQueryInputUtil.parseCompleteInformation("completeInformation", t_root.getAttributeValue("completeInformation")));
        // format
        t_result.setFormat(WggdsQueryInputUtil.parseFormat(t_root.getAttributeValue("format")));
        // operation 
        String t_value = t_root.getAttributeValue("operation");
        if ( t_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute operation.");
        }
        t_result.setOperation( ListOperation.forOperation(t_value) );
        t_value = t_root.getAttributeValue("listOne");
        if ( t_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute listOne.");
        }
        t_result.setListOne(t_value);
        t_value = t_root.getAttributeValue("listTwo");
        if ( t_result.getOperation().equals(ListOperation.NOT) )
        {
            if ( t_value != null )
            {
                throw new WggdsQueryExcpetion("Attribute listTwo is not allowed for operation NOT.");
            }
        }
        else
        {
            if ( t_result == null )
            {
                throw new WggdsQueryExcpetion("Missing attribute listTwo.");    
            }
        }
        t_result.setListTwo(t_value);
        return t_result;
    }

    private static CompleteInformation parseCompleteInformation(String a_attributeName, String a_value) throws WggdsQueryExcpetion
    {
        if ( a_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute " + a_attributeName + ".");
        }
        try
        {
            return CompleteInformation.forName(a_value.trim());
        } 
        catch (Exception t_tExection)
        {
            throw new WggdsQueryExcpetion("Invalid value for attribute " + a_attributeName + " (not a number).");
        }
    }

    /**
     * <?xml version="1.0" encoding="UTF-8"?>
     * <query namespace="namespace_of_the_database" glydeVersion="1.0" format="xml">
     *      <structure id="carbNxxx"/>
     *      <structure id="carbNxxx"/>
     *      <structure id="carbNxxx"/>
     * </query>
     * @throws WggdsQueryExcpetion 
     */
    @SuppressWarnings("unchecked")
    public static IdListQuery parseIdListQuery(String a_query) throws WggdsQueryExcpetion
    {
        IdListQuery t_result = new IdListQuery();
        SAXBuilder builder = new SAXBuilder();
        Document t_objDocument = null;
        try
        {
            t_objDocument = builder.build(new StringReader(a_query));
        }
        catch (Exception t_exection)
        {
            throw new WggdsQueryExcpetion(t_exection.getMessage(),t_exection);
        }
        Element t_root = t_objDocument.getRootElement();
        if ( !t_root.getName().equals("query") )
        {
            throw new WggdsQueryExcpetion("Missing query tag in xml.");
        }
        // format
        t_result.setFormat(WggdsQueryInputUtil.parseFormat(t_root.getAttributeValue("format")));
        // namespace
        String t_value =  t_root.getAttributeValue("namespace");
        if ( t_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute namespace.");
        }
        t_result.setNamespace(t_value);
        // glyde version
        t_result.setGlydeVersion(t_root.getAttributeValue("glydeVersion"));
        // structure list
        List<Element> t_childs = t_root.getChildren("structure");
        if ( t_childs.size() < 1 )
        {
            throw new WggdsQueryExcpetion("Missing structure tag.");
        }
        for (Element t_element : t_childs)
        {
            t_result.addId(WggdsQueryInputUtil.parseStructureId(t_element));
        }
        return t_result;
    }

    private static String parseStructureId(Element a_element) throws WggdsQueryExcpetion
    {
        String t_value = a_element.getAttributeValue("id");
        if ( t_value == null )
        {
            throw new WggdsQueryExcpetion("Missing attribute id in structure tag.");
        }
        t_value = t_value.trim();
        if ( t_value.length() == 0 )
        {
            throw new WggdsQueryExcpetion("Missing attribute id in structure tag.");
        }
        return t_value;
    }
}
